#!/usr/bin/env python
import json
import os
import shutil
import sys

from PIL import Image

def _is_positive_integer(value):
    return type(value) is int and value >= 0
    
def _is_positive_number(value):
    return type(value) in (int, float) and value >= 0
    
def _validate_mount_config(specs):
    if type(specs['centre']) is not bool:
        raise ValueError("Central-type-specifier must be a boolean value")

    if not _is_positive_integer(specs['width']):
        raise ValueError("Width must be a positive integer, expressed in weapon-slots")

    if not _is_positive_integer(specs['height']):
        raise ValueError("Height must be a positive integer, expressed in weapon-slots")

def _validate_specs_config(specs, centre):
    if not _is_positive_integer(specs['integrity']):
        raise ValueError("Integrity must be a positive integer, expressed in damage units")
        #Below 75%, weapons may require more time to cool down or more energy
        #Below 50%, weapons may sometimes fail to acquire locks
        #Below 25%, weapons may misfire, wasting resources
        #At 0%, weapons are rendered inoperable

    if not _is_positive_number(specs['mass']):
        raise ValueError("Mass must be a positive number, expressed in tonnes")

    if not centre and not _is_positive_integer(specs['crew']):
        raise ValueError("Crew must be a positive integer, expressed in headcount")

    if not centre and not _is_positive_number(specs['angle']):
        raise ValueError("Angle must be a positive number, expressed in degrees")
        
    if not _is_positive_number(specs['minRange']):
        raise ValueError("Minimum range must be a positive number, expressed in metres")
        #Apparently, the Enterprise D was about 650m long
        #A ratio of 5m/px might be reasonable

    if not _is_positive_number(specs['maxRange']):
        raise ValueError("Maximum range must be a positive number, expressed in metres")

    #All damage values are per impact or per beam per second
    if not _is_positive_number(specs['shieldDamage']):
        raise ValueError("Shield damage must be a positive number, expressed in damage units")

    if not _is_positive_number(specs['hullDamage']):
        raise ValueError("Hull damage must be a positive number, expressed in damage units")

    if not _is_positive_number(specs['subsystemDamage']):
        raise ValueError("Subsystem damage must be a positive number, expressed in damage units")

    if specs.get('colour') is not None:
        colour = [float(c) for c in specs['colour'] if type(c) in (int, float) and 0.0 <= c <= 1.0]
        if not len(colour) == 4:
            raise ValueError("Projectile-colour not specified as a quadruple of intensities between 0 and 1")
        specs['colour'] = colour #Sanitise it
        
def _validate_type_config_energy(specs):
    if not _is_positive_number(specs['heatGeneration']):
        raise ValueError("Heat generation must be a positive number, expressed in kilowatts")
        #While active, heat is built up at this rate; while inactive, heat dissipates at the same rate

    if not _is_positive_number(specs['heatLimit']):
        raise ValueError("Heat limit must be a positive number, expressed in kilojoules")
        #When this threshold is reached, the weapon must cool down

    if not _is_positive_number(specs['cooldown']):
        raise ValueError("Cooldown must be a positive number, expressed in seconds")
        #Upon overheating, the weapon will be back to 0 heat after this period elapses

    if not _is_positive_number(specs['powerDraw']):
        raise ValueError("Power-draw must be a positive number, expressed in megawatts")
        #Amount of energy consumed per second; lasers have no battery effects
        
def _validate_type_config_laser(specs, centre):
    _validate_type_config_energy(specs)
    
    #Lasers implicitly obey inverse-square (I think)
    
def _validate_type_config(specs, centre):
    if specs['type'] == 'laser': #Energy-weapon
        _validate_type_config_laser(specs, centre)
    elif specs['type'] == 'particle': #Capacitor-weapon
        _validate_type_config_particle(specs, centre)
    elif specs['type'] == 'gauss': #Projectile-weapon
        _validate_type_config_gauss(specs, centre)
    else:
        raise ValueError("Unknown weapon-type: " + specs['type'])

def _validate_generic_config(specs):
    if not _is_positive_integer(specs['baseCost']):
        raise ValueError("Base cost must be a positive integer, expressed in SPAAAAAAACE-credits")

def process_weapon(source_path, dest_path):
    specs = json.loads(open(source_path + 'specs.json').read())
    _validate_mount_config(specs['mount'])
    _validate_specs_config(specs['specs'], specs['mount']['centre'])
    _validate_type_config(specs['type'], specs['mount']['centre'])
    _validate_generic_config(specs['generic'])
    
    if not os.path.isdir(dest_path):
        os.mkdir(dest_path)
    if specs['mount']['centre']:
        if specs['mount']['width'] == specs['mount']['height']:
            shutil.copy(source_path + "shell.png", dest_path + "centre.png")
        else:
            shutil.copy(source_path + "shell.png", dest_path + "vertical.png")
            image = Image.open(source_path + "shell.png")
            image.rotate(270).save(dest_path + "horizontal.png")
    else:
        shutil.copy(source_path + "shell.png", dest_path + "up.png")
        image = Image.open(source_path + "shell.png")
        image.rotate(90).save(dest_path + "left.png")
        image.rotate(180).save(dest_path + "down.png")
        image.rotate(270).save(dest_path + "right.png")
        
    open(dest_path + "weapon.json", 'w').write(json.dumps(specs, separators=(',', ':')))
    
if __name__ == '__main__':
    base_path = os.path.abspath('.') + os.path.sep
    if not os.path.isdir(base_path + 'weapons_dist'):
        os.mkdir(base_path + 'weapons_dist')

    source_path = base_path + 'weapons' + os.path.sep
    dest_path = base_path + 'weapons_dist' + os.path.sep

    weapons = None
    if len(sys.argv) > 1:
        weapons = sys.argv[1:]
    else:
        weapons = [f for f in os.listdir(source_path) if os.path.isdir(source_path + f)]
    for weapon in weapons:
        print "Processing " + weapon + "..."
        process_weapon(source_path + weapon + os.path.sep, dest_path + weapon + os.path.sep)
        
